using System;
using System.Collections.Generic;
using System.Text;
using System.CodeDom.Compiler;
using System.IO;
using System.Collections.Specialized;
using System.Reflection;
using System.CodeDom;
using WcfSamples.DynamicProxy;
using System.ServiceModel.Description;
using Microsoft.CSharp;
using System.Diagnostics;
using System.ServiceModel;
using Spike.ContractBuilder.Host;
using System.ServiceModel.Channels;

namespace Spike.ContractBuilder
{
    public class DynamicHostFactory
    {
        private CodeCompileUnit codeCompileUnit;

        private Assembly _assembly;

        public Assembly Assembly
        {
            get { return _assembly; }
            set { _assembly = value; }
        }

        private string hostCode;

        private ContractDescription _contract;

        public ContractDescription Contract
        {
            get { return _contract; }
            set { _contract = value; }
        }

        private string _location;

        public string Location
        {
            get { return _location; }
            set { _location = value; }
        }


        public DynamicHostFactory(Type contract)
        {
            _contract = System.ServiceModel.Description.ContractDescription.GetContract(contract);

            CreateHost();
            CompileHost();
        }

        private void CreateHost()
        {
            string methodContent = string.Empty;

            // Create the CodeCompileUnit to contain the code
            codeCompileUnit = new CodeCompileUnit();
            // Assign the desired namespace
            CodeNamespace cns = new CodeNamespace("Spike.ContractBuilder.Host");
            cns.Imports.Add(new CodeNamespaceImport("System"));
            cns.Imports.Add(new CodeNamespaceImport("System.ServiceModel"));
            cns.Imports.Add(new CodeNamespaceImport("System.CodeDom"));
            cns.Imports.Add(new CodeNamespaceImport("Spike.ContractBuilder"));

            codeCompileUnit.Namespaces.Add(cns);

            // Create the new class declaration
            CodeTypeDeclaration parentClass = new CodeTypeDeclaration(_contract.Name + "Service");
            parentClass.BaseTypes.Add(_contract.ContractType);

            cns.Types.Add(parentClass);
           
            // loop through all the operations on the class
            foreach (OperationDescription operation in _contract.Operations)
            {
                CodeMemberMethod method = new CodeMemberMethod();
                method.Name = operation.Name;
                method.Attributes = MemberAttributes.Public | MemberAttributes.Final;

                // parameters that will be passed into the StubServiceHost
                // this will be an object[] of the paremeters of the create method
                StringBuilder parameters = new StringBuilder("new object[] {");
                Type typeOfResponse = null;

                foreach (MessageDescription message in operation.Messages)
                {
                    if (message.Direction == MessageDirection.Input)
                    {
                        if (message.MessageType == null)
                        {
                            foreach (MessagePartDescription body in message.Body.Parts)
                            {
                                CodeParameterDeclarationExpression arg = new
                                   CodeParameterDeclarationExpression(body.Type,
                                                                      body.Name);
                                parameters.Append(body.Name + ", ");
                                method.Parameters.Add(arg);
                            }
                        }
                        else
                        {
                            CodeParameterDeclarationExpression arg = new
                               CodeParameterDeclarationExpression(message.MessageType,
                                                                  message.MessageType.Name);
                            parameters.Append(message.MessageType.Name + ", ");
                            method.Parameters.Add(arg);
                        }
                    }
                    else
                    {
                        if (message.MessageType == null)
                        {
                            method.ReturnType = new CodeTypeReference(message.Body.ReturnValue.Type);
                            typeOfResponse = message.Body.ReturnValue.Type;                            
                        }
                        else
                        {
                            method.ReturnType = new CodeTypeReference(message.MessageType);
                            typeOfResponse = message.MessageType;
                        }
                    }
                }

                // remove the last ", "
                if (parameters[parameters.Length - 2] == ',')
                    parameters.Remove(parameters.Length - 2, 2);

                parameters.Append("}");
                methodContent = GenerateMethodBody(method.Name, typeOfResponse, parameters.ToString());

                // Add the desired code to the method body
                CodeSnippetStatement methodBody =
                   new CodeSnippetStatement(methodContent);

                method.Statements.Add(methodBody);
                parentClass.Members.Add(method);
            }

            CSharpCodeProvider provider = new CSharpCodeProvider();
            CodeGeneratorOptions options = new CodeGeneratorOptions();
            options.BlankLinesBetweenMembers = false;
            options.IndentString = "\t";
            StringWriter sw = new StringWriter();
            try
            {
                provider.GenerateCodeFromCompileUnit(codeCompileUnit, sw, options);
                sw.Flush();
            }
            finally
            {
                sw.Close();
            }

            StringBuilder sb = sw.GetStringBuilder();
            hostCode = sb.ToString();
        }
       
        /// <summary>
        /// Based on the return paramenter, generate a method body
        /// </summary>
        /// <param name="codeTypeReference"></param>
        /// <returns></returns>
        private string GenerateMethodBody(string method, Type type, string parameters)
        {
            if (type.FullName == "System.Void")
                return string.Empty;

            StringBuilder sb = new StringBuilder();

            sb.Append("Type typeOfResponse = Type.GetType(\"");
            sb.Append(type.AssemblyQualifiedName);
                       
            sb.AppendLine("\");");

            sb.AppendLine("Type typeOfHost = this.GetType();");

            sb.Append("return (");

            if (type.IsGenericType)
            {
                string[] names = type.FullName.Split('`');
                sb.Append(names[0]);
                sb.Append("<");
                sb.Append(names[1].Substring(3, names[1].IndexOf(",") - 3));
                sb.Append(">");
            }
            else
            {
                sb.Append(type.FullName);
            }           

            sb.Append(") StubHostService.Instance.GetObject(typeOfHost, \"");
            sb.Append(method);
            sb.Append("\", ");
            sb.Append(parameters);
            sb.Append(", typeOfResponse); ");                      
            
            return sb.ToString();
        }

        private void CompileHost()
        {
            CSharpCodeProvider provider = new CSharpCodeProvider();
            CompilerParameters options = new CompilerParameters();
            options.IncludeDebugInformation = false;
            options.GenerateExecutable = false;
            options.GenerateInMemory = false;

            AddAssemblyReference(typeof(System.ServiceModel.ServiceHost).Assembly, options.ReferencedAssemblies);
            AddAssemblyReference(typeof(System.Runtime.Serialization.IExtensibleDataObject).Assembly, options.ReferencedAssemblies);
            AddAssemblyReference(_contract.ContractType.Assembly, options.ReferencedAssemblies);
            AddAssemblyReference(typeof(CodeTypeReference).Assembly, options.ReferencedAssemblies);
            AddAssemblyReference(typeof(StubHostService).Assembly, options.ReferencedAssemblies);

            CompilerResults results =
               provider.CompileAssemblyFromSource(options, hostCode);
            provider.Dispose();       

            if (results.Errors.Count == 0)
            {
                _assembly = results.CompiledAssembly;
                _location = results.PathToAssembly;
            }
        }

        static void AddAssemblyReference(Assembly referencedAssembly,
            StringCollection refAssemblies)
        {
            string path = Path.GetFullPath(referencedAssembly.Location);
            string name = Path.GetFileName(path);
            if (!(refAssemblies.Contains(name) ||
                  refAssemblies.Contains(path)))
            {
                refAssemblies.Add(path);
            }
        }


       
    }
}
